package com.wn.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wn.common.result.Result;
import com.wn.common.result.enums.impl.ResultCode;
import com.wn.common.utils.JwtTemplate;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.validation.constraints.Size;
import java.time.Duration;

/**
 * @author yellow Docter
 * @date 2023 -07-24
 * @desc gateway中的全局过滤器
 * 1、 实现 GlobalFilter  这个接口
 * 2、 重写 filter 方法
 * <p>
 * gateway 是基于  webflux 框架实现  处理请求 响应结果
 * 他的操作 和 springmvc的操作不一致
 * <p>
 * GatewayFilterChain 拦截器
 * filter(exchange) 是放行
 * <p>
 * ServerWebExchange 交换机
 * ServerWebExchange 命名为 服务网络交换器 ，存放着重要的请求-响应属性、请求实例和响应实例等等
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter {

    @Autowired
    private JwtTemplate jwtTemplate;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     *  鉴权的流程:
     *     1、登录后 返回一个token
     *          1、输入手机号码   获取验证码
     *                   短信接入你是怎么做的？
     *          2、校验验证码  实现登录
     *                   怎么防止短信轰炸?
     *          3、通过jwt 生成  token
     *                   jwt 是啥? 有哪些东西 ？
     *          4、把token存储到redis中 (计时 token 是否过期)
     *                    redis 的数据结构 存储多久?
     *          5、把token 返回给前端
     *                    前后端交互
     *
     *     2、网关中鉴权
     *          1、gateway中 开发 全局过滤器  实现 GlobalFilter
     *                过滤器\gateway 是什么?
     *          2、拿到请求 拿到所有的URL 通过Url的判断、对指定的路径进行鉴权
     *          3、通过请求拿到  携带的认证信息、拿到token
     *          4、对token 进行 校验
     *          5、对token 进行 解析 拿到 ID、phone
     *          6、去redis中获取  token 判断是否过期没有
     *          7、头信息 (每个微服务中可以拿到 userid、phone 获取用户信息)
     *          8、redis的续签  (设置token的新的过期实现)
     *          9、拦截器放行
     *
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //请求
        ServerHttpRequest request = exchange.getRequest();
        //响应
        ServerHttpResponse response = exchange.getResponse();
        String path = request.getURI().getPath();
        log.info("全局过滤器....,请求的url:===>{}", path);

        /**
         * token 的校验
         *   1、是否所有的请求都会校验token (可以通过url进行判断)
         *          有一些路径不行校验token
         *          有一些路径需要token
         *
         *   2、如何按到 请求中携带的token
         */
        //路径校验的工具类
        AntPathMatcher pathMatcher = new AntPathMatcher();

        // 请求路径如：/xxx/auth/xxx/100
        boolean match = pathMatcher.match("/**/auth/**", path);
        if (match) {
            //进行token的校验
            log.info("需要token的校验....");
            //拿到token

            //拿到头信息中的数据 token
            //Authorization:Bearer <token>
            //拿到认证信息
            String authorization = request.getHeaders().getFirst("Authorization");
            log.info("通过头信息拿到Authorization....:{}", authorization);

            if (StringUtils.isEmpty(authorization)) {
                log.info("拿到认证信息失败...");
                // 抛出异常
                return error(response,"拿到认证信息失败...");
            }

            //截取拿到认证信息中的token的数据
            String token = authorization.replace("Bearer ", "");
            log.info("通过头信息拿到token....:{}", token);

            // token 下一步操作
            // 1、校验token
            boolean verify = false;
            try {
                verify =jwtTemplate.verify(token);
            } catch (Exception e) {
                return error(response,"token非法...");
            }
            if (!verify) {
                log.info("token非法...");
                // 抛出异常  不能使用全局异常 (没有springmvc的一依赖)
                return error(response,"token非法...");
            }
            // 2、解析token (拿到载荷)    用户ID
            // 拿到ID、通过ID 去 redis 获取token是否过期没有
            Object obj = null;
            Object phone = null;
            try {
                obj =  jwtTemplate.getPayload(token, "USER_ID");
                phone =  jwtTemplate.getPayload(token, "PHONE");

            } catch (Exception e) {
                return error(response,"redis的token过期了...");
            }

            // 3、判断 token是否过期没有  (通过用户ID查询 redis中是否存在 token ) (token 过期了)
            String redisKey = "USER_ID:TOKEN:" + obj.toString();
            String redisToken = stringRedisTemplate.opsForValue().get(redisKey);

            if (StringUtils.isEmpty(redisToken)) {
                log.info("redis的token过期了...");
                // 抛出异常
                return error(response,"redis的token过期了...");
            }

            //在请求中 添加一个头信息
            // 头重写  每个服务中都有 userId
            request.mutate().header("userId",obj.toString());
            request.mutate().header("phone",phone.toString());

            // 4、token的续签
            //设置 新的过期时间
//            stringRedisTemplate.expire(redisKey, Duration.ofMinutes(30L));

        }
        return chain.filter(exchange);
    }

    // 自动处理检查异常的注解
    // webflux 技术   webflux  + netty (异步处理)
    @SneakyThrows
    private Mono<Void> error(ServerHttpResponse response,String msg)  {

        Result<Object> result = Result.fail(msg);
        ObjectMapper objectMapper = new ObjectMapper();
        byte[] bytes = objectMapper.writeValueAsBytes(result);
        DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
        return response.writeWith(Mono.just(dataBuffer));

    }
}
